Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
name: CI

on:
push:
branches:
- master
- "codex/**"
- "harden-*"
pull_request:
workflow_dispatch:

jobs:
ci:
name: CI
runs-on: ubuntu-latest

steps:
- name: Check out repository
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.11"

- name: Install test and build tooling
run: |
python -m pip install --upgrade pip
python -m pip install pytest build twine

- name: Run test suite
run: python -m pytest -q

- name: Build distributions
run: python -m build

- name: Check distribution metadata
run: python -m twine check dist/*

- name: Smoke test installed package
run: |
python -m venv .venv-ci
.venv-ci/bin/python -m pip install dist/*.whl
.venv-ci/bin/sessionanchor --help
.venv-ci/bin/python -m sessionanchor --help
22 changes: 18 additions & 4 deletions src/sessionanchor/init.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,25 +77,39 @@ def run_init(repo_root: str | None = None, skip_index: bool = False) -> None:

conn.close()

# Verify CLI is discoverable on PATH
cli_on_path = shutil.which("sessionanchor") is not None
quick_start_prefix = "sessionanchor"

print()
print("=" * 60)
print(" SessionAnchor initialized!")
print("=" * 60)

if not cli_on_path:
quick_start_prefix = "<launcher> sessionanchor"
print()
print(" [!] NOTE: 'sessionanchor' was not found on PATH.")
print(" Use the same launcher for future commands, e.g.")
print(" 'uvx sessionanchor ...', 'pipx run sessionanchor ...',")
print(" or 'python -m sessionanchor ...'.")
print(" Replace <launcher> in the examples below with the one you use.")

print()
print(" Quick start:")
print()
print(" # At session start - get your briefing")
print(" sessionanchor boot")
print(f" {quick_start_prefix} boot")
print()
print(" # Save a decision")
print(' sessionanchor save add --tier L1 --category decision \\')
print(f' {quick_start_prefix} save add --tier L1 --category decision \\')
print(' --title "Chose SQLite" --content "Zero deps, local-only"')
print()
print(" # Search memory")
print(' sessionanchor query "deployment"')
print(f' {quick_start_prefix} query "deployment"')
print()
print(" # End of session")
print(' sessionanchor save session-end --summary "Built auth flow"')
print(f' {quick_start_prefix} save session-end --summary "Built auth flow"')
print()
print(" CLAUDE.md has been configured to instruct Claude to use")
print(" this memory system automatically.")
Expand Down
87 changes: 56 additions & 31 deletions src/sessionanchor/templates/claude_md_section.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,53 +2,78 @@
## Context Management

This project uses [SessionAnchor](https://github.com/calesthio/SessionAnchor) for session continuity.
**Do NOT read large context files at session start.** Use the memory system instead.

### Session Start Protocol
### MANDATORY: Session Start (do this FIRST, before any other work)

Run this before doing anything else:
```bash
# 1. Get your briefing (~2000 tokens instead of reading all project files)
sessionanchor boot
```
- If `command not found`: use the same launcher you used for SessionAnchor for all commands below:
`uvx sessionanchor ...`, `pipx run sessionanchor ...`, or `python -m sessionanchor ...`.
If SessionAnchor is not installed yet, run `pip install sessionanchor`, then retry.
- If `No memory database found`: run the matching init command with the same launcher
(`sessionanchor init`, `uvx sessionanchor init`, `pipx run sessionanchor init`,
or `python -m sessionanchor init`), then boot the same way.
- Read the output — it contains prior decisions, active work, and blockers from previous sessions.
- Do NOT skip this. Do NOT read large project files as a substitute.

# 2. If you need deeper context on any topic during the session:
sessionanchor query "search terms"
### MANDATORY: Save After Every Substantive Action

# 3. If you need to understand code structure:
sessionanchor index find "topic"
sessionanchor index map
```
You MUST save context after each of these — do NOT batch, do NOT wait for session end:
- A feature, fix, or deliverable is completed
- A decision is made (technical choice, architecture, tradeoff)
- A blocker is discovered
- You learn something non-obvious about the codebase

**How to save:**
- If your platform supports background agents: spawn one with the save command
- Otherwise: run the save command inline before continuing

### Context Saving (MANDATORY - do not skip)
```bash
# Decision made
sessionanchor save add --tier L1 --category decision --title "..." --content "..."

**Save context continuously, not just at session end.** After completing any substantive work
(a feature, a fix, a decision, a research task), immediately write the relevant context to memory.
Do NOT wait for the user to ask. Do NOT wait for "end of session." The user may close the
window at any time.
# Task/feature completed (marks the action item as completed)
sessionanchor save complete --title "..." --category action_item

**How to save: use a subagent.** Do NOT block the main conversation. Spawn a background agent
with the save commands and continue working.
# Significant event or milestone worth logging
sessionanchor save add --tier L1 --category session_log --title "..." --content "..."

**When to save (trigger immediately after any of these):**
- A decision is made -> `sessionanchor save add --tier L1 --category decision --title "..." --content "..."`
- A task is completed -> `sessionanchor save complete --title "..." --category action_item`
- A new blocker is discovered -> `sessionanchor save add --tier L1 --category blocker --title "..." --content "..."`
- New technical knowledge -> `sessionanchor save add --tier L2 --category technical_note --title "..." --content "..."`
- Significant code changes -> `sessionanchor save add --tier L1 --category session_log --title "..." --content "..."`
# Blocker found
sessionanchor save add --tier L1 --category blocker --title "..." --content "..."

**Session summary (run when work naturally winds down):**
```bash
sessionanchor save session-end --summary "What was accomplished"
# Technical knowledge learned
sessionanchor save add --tier L2 --category technical_note --title "..." --content "..."
```

### Memory Tiers
**Self-check: if you have completed 3+ tasks without running a single save command, stop and save now.**

**Scope guard: only save context that is relevant to THIS project.** If the user asks you to
work on an unrelated repo, fix an external tool, or do research that does not affect this
codebase, do NOT save that work here — it is noise. Example: if this project is a web app and
the user asks you to debug a CLI tool in a different repo mid-session, that debugging context
does not belong in this project's memory.

- **L0** (~500 tokens): Identity, current phase, top priorities. Always loaded.
- **L1** (~1500 tokens): Recent decisions, active action items, blockers. Loaded at boot.
- **L2** (on-demand): Full history, completed items, deep technical notes. Query when needed.
### Session End

### Re-indexing
When work winds down:
```bash
sessionanchor save session-end --summary "What was accomplished"
```

### Reference: Querying Deep Context

Run after major structural changes:
```bash
# Search memory for a topic
sessionanchor query "search terms"

# Re-index after major structural changes (new directories, renamed modules)
sessionanchor index
```

### Reference: Memory Tiers

- **L0** (~500 tokens): Identity, current phase, top priorities. Always loaded at boot.
- **L1** (~1500 tokens): Recent decisions, active items, blockers. Loaded at boot.
- **L2** (on-demand): Full history, completed items, deep technical notes. Use `sessionanchor query` to retrieve.
2 changes: 1 addition & 1 deletion tests/test_e2e_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,7 @@ def test_index_find_and_map_cover_repo_workflow(repo: Path):
conn.close()

assert "Indexed" in index_result.stdout
assert "src\\auth.py" in find_result.stdout
assert str(Path("src") / "auth.py") in find_result.stdout
assert ".env" not in find_result.stdout
assert "## Modules" in map_result.stdout
assert "## File Tree" in map_result.stdout
Expand Down
32 changes: 32 additions & 0 deletions tests/test_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,38 @@ def test_init_creates_claude_md(temp_repo):
assert "sessionanchor boot" in content


def test_claude_md_has_mandatory_sections(temp_repo):
run_init(repo_root=temp_repo, skip_index=True)
claude_md = os.path.join(temp_repo, "CLAUDE.md")
with open(claude_md) as f:
content = f.read()
# Mandatory start protocol
assert "MANDATORY: Session Start" in content
# Mandatory save protocol
assert "MANDATORY: Save After Every Substantive Action" in content
# Self-check instruction
assert "Self-check" in content
# Install fallback
assert "pip install sessionanchor" in content
# python -m fallback
assert "python -m sessionanchor" in content
# runner fallback for ephemeral installs
assert "uvx sessionanchor" in content
# completion wording should stay accurate
assert "marks the action item as completed" in content
assert "leaves the boot briefing" not in content


def test_init_warns_when_cli_not_on_path(temp_repo, capsys, monkeypatch):
import sessionanchor.init as init_module
monkeypatch.setattr(init_module.shutil, "which", lambda _name: None)
run_init(repo_root=temp_repo, skip_index=True)
captured = capsys.readouterr()
assert "not found on PATH" in captured.out
assert "python -m sessionanchor" in captured.out
assert "<launcher> sessionanchor" in captured.out


def test_init_patches_existing_claude_md(temp_repo):
claude_md = os.path.join(temp_repo, "CLAUDE.md")
with open(claude_md, "w") as f:
Expand Down
Loading